package plugin

import (
	"bytes"
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"sort"

	log "github.com/Sirupsen/logrus"
	"github.com/zstackio/zstack-vyos/utils"
)

const (
	DNSMASQ_BIN_PATH         = "/usr/sbin/dnsmasq -x /var/run/dnsmasq.pid -u dnsmasq -7 /etc/dnsmasq.d"
	DNSMASQ_CONF_PATH        = "/etc/dnsmasq.conf"
	DNSMASQ_CONF_PATH_TEMP   = "/home/vyos/zvr/dnsmasq.conf"
	DNSMASQ_RESOLV_FILE      = "/etc/resolv.conf"
	DNSMASQ_RESOLV_FILE_TEMP = "/home/vyos/zvr/resolv.conf"
	DNSMASQ_PID_FILE         = "/var/run/dnsmasq.pid"
	DNSMASQ_PID_FILE_TEMP    = "/home/vyos/zvr/.zstack_config/dnsmasq.pid"
)

const dnsmasqTemplate = `#
# autogenerated by ZStack, DO NOT MODIFY IT
#
log-facility=/var/log/dnsmasq.log
no-poll
edns-packet-max=4096
cache-size=150
{{ range $index, $name := .NicNames }}
interface={{$name}}
{{ end }}
resolv-file=/etc/resolv.conf
`

const resolvTemplate = `#
# autogenerated by ZStack, DO NOT MODIFY IT
#
{{ range $index, $ip := .DnsServers }}
nameserver {{$ip}}
{{ end }}`

type DnsmasqConf struct {
	NicNames   []string
	DnsServers []string
}

func NewDnsmasq(nics, servers map[string]string) *DnsmasqConf {
	var nicNames, ips []string
	for nic, _ := range nics {
		nicNames = append(nicNames, nic)
	}
	sort.Strings(nicNames)
	for ip, _ := range servers {
		ips = append(ips, ip)
	}

	return &DnsmasqConf{NicNames: nicNames, DnsServers: ips}
}

func (d *DnsmasqConf) RestartDnsmasq() error {
	if len(d.NicNames) == 0 {
		bash := utils.Bash{
			Command: "pkill -9 dnsmasq",
			Sudo:    true,
		}
		bash.Run()
		return nil
	}
	err := templateHandler(d, DNSMASQ_CONF_PATH_TEMP, "dnsmasq.conf", dnsmasqTemplate)
	utils.PanicOnError(err)
	err = templateHandler(d, DNSMASQ_RESOLV_FILE_TEMP, "resolv.conf", resolvTemplate)
	utils.PanicOnError(err)
	bash := utils.Bash{
		Command: fmt.Sprintf("mv %s %s", DNSMASQ_RESOLV_FILE_TEMP, DNSMASQ_RESOLV_FILE),
		Sudo:    true,
	}
	err = bash.Run()
	utils.PanicOnError(err)

	if dnsmasqIsRunning() &&
		diffConfigFile(DNSMASQ_CONF_PATH_TEMP, DNSMASQ_CONF_PATH) &&
		diffConfigFile(DNSMASQ_PID_FILE_TEMP, DNSMASQ_PID_FILE) {
		log.Debugf("[dnsmasq]: dnsmasq.conf not changed, just reload resolv.conf")
		bash := utils.Bash{
			Command: fmt.Sprintf("cat %s | xargs kill -HUP", DNSMASQ_PID_FILE),
			Sudo:    true,
		}
		bash.Run()

		return nil
	}

	bash = utils.Bash{
		Command: fmt.Sprintf("mv %s %s", DNSMASQ_CONF_PATH_TEMP, DNSMASQ_CONF_PATH),
		Sudo:    true,
	}
	err = bash.Run()
	utils.PanicOnError(err)

	err = utils.Retry(func() error {
		bash = utils.Bash{
			Command: fmt.Sprintf("pkill -9 dnsmasq; %s", DNSMASQ_BIN_PATH),
			Sudo:    true,
		}
		return bash.Run()
	}, 5, 1)
	utils.PanicOnError(err)

	if ok, err := utils.PathExists(DNSMASQ_PID_FILE_TEMP); err != nil || !ok {
		if err := utils.MkdirForFile(DNSMASQ_PID_FILE_TEMP, 0755); err != nil {
			return err
		}
	}
	if err := utils.CopyFile(DNSMASQ_PID_FILE, DNSMASQ_PID_FILE_TEMP); err != nil {
		return err
	}

	return nil
}

func templateHandler(d *DnsmasqConf, filepath string, name string, t string) error {
	if filepath == "" || name == "" || t == "" {
		return errors.New("args can not be empty")
	}
	tmpl, err := template.New(name).Parse(t)
	if err != nil {
		return err
	}

	buf := bytes.Buffer{}
	if err := tmpl.Execute(&buf, d); err != nil {
		return err
	}

	return ioutil.WriteFile(filepath, buf.Bytes(), 0664)
}

func diffConfigFile(srcfile string, dstfile string) bool {
	var src, dst []byte
	var err error
	if src, err = ioutil.ReadFile(srcfile); err != nil {
		return false
	}
	if dst, err = ioutil.ReadFile(dstfile); err != nil {
		return false
	}

	return bytes.Equal(src, dst)
}

func dnsmasqIsRunning() bool {
	bash := utils.Bash{
		Command: fmt.Sprintf("ps -ef | grep '%s' | grep -v grep", DNSMASQ_BIN_PATH),
		Sudo:    true,
	}
	ret, _, _, _ := bash.RunWithReturn()

	return ret == 0
}
